import { Renderer } from './renderer';
import { objectWithShadowShader } from './shaders';
import { createGPUBuffer, shaderModuleFromCode } from './utils';

export class Plane {
  renderer: Renderer;
  positionBuffer: GPUBuffer;
  normalBuffer: GPUBuffer;
  uniformBindGroup: GPUBindGroup;
  pipeline: GPURenderPipeline;

  constructor(renderer: Renderer) {
    this.renderer = renderer;

    const device = renderer.device;

    const shaderModule = shaderModuleFromCode(device, objectWithShadowShader);

    const positions = new Float32Array([-100, 0, -100, 100, 0, -100, -100, 0, 100, 100, 0, 100]);

    const normals = new Float32Array([0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0]);

    this.positionBuffer = createGPUBuffer(
      device,
      positions,
      GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
    );
    this.normalBuffer = createGPUBuffer(
      device,
      normals,
      GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
    );

    const uniformBindGroupLayout = device.createBindGroupLayout({
      label: 'plane',
      entries: [
        { binding: 0, visibility: GPUShaderStage.VERTEX, buffer: {} },
        { binding: 1, visibility: GPUShaderStage.VERTEX, buffer: {} },
        { binding: 2, visibility: GPUShaderStage.VERTEX, buffer: {} },
        { binding: 3, visibility: GPUShaderStage.VERTEX, buffer: {} },
        { binding: 4, visibility: GPUShaderStage.VERTEX, buffer: {} },
        { binding: 5, visibility: GPUShaderStage.FRAGMENT, buffer: {} },
        { binding: 6, visibility: GPUShaderStage.FRAGMENT, buffer: {} },
        { binding: 7, visibility: GPUShaderStage.FRAGMENT, buffer: {} },
        { binding: 8, visibility: GPUShaderStage.FRAGMENT, buffer: {} },
        { binding: 9, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },
        { binding: 10, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'comparison' } },
        { binding: 11, visibility: GPUShaderStage.FRAGMENT, buffer: {} },
        { binding: 12, visibility: GPUShaderStage.FRAGMENT, buffer: {} },
      ],
    });

    const ambientUniformBuffer = createGPUBuffer(
      device,
      new Float32Array([0.15, 0.1, 0.1, 1.0]),
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );
    const diffuseUniformBuffer = createGPUBuffer(
      device,
      new Float32Array([0.55, 0.55, 0.55, 1.0]),
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );
    const specularUniformBuffer = createGPUBuffer(
      device,
      new Float32Array([0.0, 0.0, 0.0, 1.0]),
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );
    const shininessUniformBuffer = createGPUBuffer(
      device,
      new Float32Array([0.0]),
      GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
    );

    const {
      modelViewMatrixUniformBuffer,
      projectionMatrixUniformBuffer,
      normalMatrixUniformBuffer,
      lightDirectionUniformBuffer,
      viewDirectionUniformBuffer,
      lightModelViewMatrixUniformBuffer,
      lightProjectionMatrixUniformBuffer,
    } = renderer.camera;

    this.uniformBindGroup = device.createBindGroup({
      layout: uniformBindGroupLayout,
      entries: [
        { binding: 0, resource: { buffer: modelViewMatrixUniformBuffer } },
        { binding: 1, resource: { buffer: projectionMatrixUniformBuffer } },
        { binding: 2, resource: { buffer: normalMatrixUniformBuffer } },
        { binding: 3, resource: { buffer: lightDirectionUniformBuffer } },
        { binding: 4, resource: { buffer: viewDirectionUniformBuffer } },
        { binding: 5, resource: { buffer: ambientUniformBuffer } },
        { binding: 6, resource: { buffer: diffuseUniformBuffer } },
        { binding: 7, resource: { buffer: specularUniformBuffer } },
        { binding: 8, resource: { buffer: shininessUniformBuffer } },
        { binding: 9, resource: renderer.lightDepthTexture.createView() },
        { binding: 10, resource: renderer.sampler },
        { binding: 11, resource: { buffer: lightModelViewMatrixUniformBuffer } },
        { binding: 12, resource: { buffer: lightProjectionMatrixUniformBuffer } },
      ],
    });

    const layout = device.createPipelineLayout({ bindGroupLayouts: [uniformBindGroupLayout] });

    this.pipeline = device.createRenderPipeline({
      layout,
      vertex: {
        module: shaderModule,
        entryPoint: 'vs_main',
        buffers: [
          {
            attributes: [
              {
                shaderLocation: 0, // @location(0)
                offset: 0,
                format: 'float32x3',
              },
            ],
            arrayStride: 4 * 3, // sizeof(float) * 3
            stepMode: 'vertex',
          },
          {
            attributes: [
              {
                shaderLocation: 1, // @location(1)
                offset: 0,
                format: 'float32x3',
              },
            ],
            arrayStride: 4 * 3, // sizeof(float) * 3
            stepMode: 'vertex',
          },
        ],
      },
      fragment: {
        module: shaderModule,
        entryPoint: 'fs_main',
        targets: [{ format: 'bgra8unorm' }],
      },
      primitive: {
        topology: 'triangle-strip',
        frontFace: 'cw',
        cullMode: 'none',
      },
      depthStencil: {
        depthWriteEnabled: true,
        depthCompare: 'less',
        format: 'depth32float',
      },
    });
  }

  render(encoder: GPURenderPassEncoder) {
    encoder.setPipeline(this.pipeline);
    encoder.setBindGroup(0, this.uniformBindGroup);
    encoder.setVertexBuffer(0, this.positionBuffer);
    encoder.setVertexBuffer(1, this.normalBuffer);
    encoder.draw(4, 1);
  }
}
